import {Component, Input} from '@angular/core';
import { Generate} from '../Utils/Generate';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.less']
})
export class AppComponent {
  public title = 'Sudoku Game';
  public matrix: any[] = []; // 初始数组
  public orders: any[] = []; // 生成1-9的数组
  public puzzleMatrix: any[] = []; // 生成最终的数组
  public _cells: any[] = [];
  public temp: any[] = []; // 存放用户输入前的数组
  public flag = false;
  public numArr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
  constructor() {
    // this.matrix = this.generate();
    // this.matrix = this.make();
    // this.temp = this.matrix;
    const generate = new Generate();
    // console.log(this.matrix);
    // console.log(this.orders);
    // console.log(this.puzzleMatrix);
  }
  // 生成数列，全是0的二维数组
  public makeMatrix(value: number) {
     const arr = Array.from({length: 9}, () => new Array(9).fill(value));
     return arr;
  }
  // 洗牌算法
  // Fisher-Yates 洗牌算法，Lodash 中有实现
  public shuffle<T>(array: Array<T>): Array<T> {
    const length = array.length;
    const endIndex = length - 1;
    for (let i = 0; i < endIndex; i++) {
      const j = i + Math.floor(Math.random() * (length - i));
      [array[i], array[j]] = [array[j], array[i]];
    }
    return array;
  }
  public generate(): any[] {
    while (!this.internalGenerate()) {
      console.warn('rebuild');
    }
    return this.matrix;
  }
  public internalGenerate(): boolean {
    this.matrix = this.makeMatrix(0);
    this.orders = Array.from({length: 9}).map(() => Array.from({length: 9}, (x, i) => i))
      .map(row => this.shuffle(row));
    return Array.from({length: 9}).every((n, i) => this.fillRow(i + 1, 0));
  }

  public fillRow(n: number, rowIndex: number): boolean {
    if (rowIndex >= 9) {
      return true;
    }
    const row = this.matrix[rowIndex];
    const orders = this.orders[rowIndex];
    for (let i = 0; i < orders.length; i++) {
      const colIndex = orders[i];
      // 如果这个位置有值，跳过
      if (row[colIndex]) {
        continue;
      }
      // 检查这个位置是否可以填入n，如果不能填，忽略
      if (!this.checkFillable(this.matrix, n, rowIndex, colIndex)) {
        continue;
      }
      row[colIndex] = n;
      // 去下一行填n，如果没填进去，就回退
      if (!this.fillRow(n, rowIndex + 1)) {
        row[colIndex] = 0;
        continue;
      }
      return true;
    }
    return false;
  }
  // 检查某个位置是否可以填入n
  public checkFillable(matrix: any[], n: number, rowIndex: number, colIndex: number) {
    const boxCoor = new BoxToolkit().convertToBoxIndex(rowIndex, colIndex);
    const boxCells = new BoxToolkit().getBoxCells(matrix, boxCoor.boxIndex);
    for (let i = 0; i < 9; i++) {
      if (matrix[i][colIndex] === n || matrix[rowIndex][i] === n || boxCells[i] === n) {
        return false;
      }
    }
    return true;
  }
  public make(level: number = 1): any[] {
    this.puzzleMatrix = this.matrix.map(row => {
      return row.map(v => Math.random() * 9 < level ? 0 : v);
    });
    // console.log(this.puzzleMatrix);
    return this.puzzleMatrix;
  }
  // 用户点击方法
  userCheck(): boolean {
    // console.log('进入check方法');
    const checker = new Checker(this.puzzleMatrix).check();
    if (checker) {
      alert('成功');
      return true;
    }
    return false;
  }

  reset() {
    this.matrix = this.temp;
  }

  answer() {
    // console.log('进入答案方法');
    this.matrix = this.puzzleMatrix;
  }
  rebuild() {
    // new AppComponent();
  }
  checkFlag(num: number) {
    // console.log(num);
    if (num == 0) {
      // console.log('xx');
      this.flag = !this.flag;
    }
  }
  changeArr(num: number) {
    // console.log(num);
  }
}

// 宫坐标工具箱
class BoxToolkit {
  // 根据给定的宫号(从0开始)获取一个数组，
  // 这个数组按先行后列从左到右的顺序包含宫内每个元素的值
  getBoxCells(matrix: any[], boxIndex: number): Array<number> {
    const startRowIndex = Math.floor(boxIndex / 3) * 3;
    const startColIndex = boxIndex % 3 * 3;
    const result: number[] = [];
    for (let i = 0; i < 9; i++) {
      const rowIndex = startRowIndex + Math.floor(i / 3);
      const colIndex = startColIndex + i % 3;
      result.push(matrix[rowIndex][colIndex]);
    }
    return result;
  }

  // 从宫坐标转换为矩阵坐标
  convertFromBoxIndex(boxIndex: number, cellIndex: number) {
    return {
      rowIndex: Math.floor(boxIndex / 3) * 3 + Math.floor(cellIndex / 3),
      colIndex: boxIndex % 3 * 3 + cellIndex % 3
    };
  }

  // 从矩阵坐标转换为宫坐标
  convertToBoxIndex(rowIndex: number, colIndex: number) {
    return {
      boxIndex: Math.floor(rowIndex / 3) * 3 + Math.floor(colIndex / 3),
      cellIndex: rowIndex % 3 * 3 + colIndex % 3
    };
  }

}
// 检查方法
class Checker {
  public matrix: any[];
  public _success: any;
  public marks: any[];

  static _checkArray(array: any[]): any[] {
    const marks: Array<boolean> = new Array(array.length);
    marks.fill(true);

    array.forEach((v, i) => {
      if (!v) {
        marks[i] = false;
        return;
      }

      if (marks[i]) {
        for (let j = i + 1; j < 9; j++) {
          if (v === array[j]) {
            marks[i] = false;
            marks[j] = false;
          }
        }
      }
    });

    return marks
      .map((v, i) => v ? -1 : i)
      .filter(index => index >= 0);
  }

  constructor(matrix: any[]) {
    this.matrix = matrix;
  }

  check(): this {
    this.marks = new AppComponent().make(5);
    this.checkRows();
    this.checkCols();
    this.checkBoxes();
    this._success = this.marks.every(row => row.every(v => v));
    return this;
  }

  private checkRows() {
    const marks = this.marks;
    this.matrix.forEach((row, rowIndex) => {
      const rowMarks = Checker._checkArray(row);
      rowMarks.forEach(colIndex => {
        marks[rowIndex][colIndex] = false;
      });
    });
  }

  private checkCols() {
    const {matrix, marks} = this;
    for (let colIndex = 0; colIndex < 9; colIndex++) {
      const cols = Array.from({ length: 9 })
        .map((v, i) => matrix[i][colIndex]);
      const colMarks = Checker._checkArray(cols);
      colMarks.forEach(rowIndex => marks[rowIndex][colIndex] = false);
    }
  }

  private checkBoxes() {
    const {matrix, marks} = this;
    const boxes = Array.from({ length: 9 })
      .map((v, i) => new BoxToolkit().getBoxCells(matrix, i));
    boxes.forEach((box, boxIndex) => {
      const boxMarks = Checker._checkArray(box);
      boxMarks.forEach(cellIndex => {
        const {rowIndex, colIndex}
          = new BoxToolkit().convertFromBoxIndex(boxIndex, cellIndex);
        marks[rowIndex][colIndex] = false;
      });
    });
  }
}
